/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package com.kitfox.svg.batik;

import java.awt.Color;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;

/**
 * <p>
 * This class provides a way to fill a shape with a circular radial color
 * gradient pattern. The user may specify 2 or more gradient colors, and this
 * paint will provide an interpolation between each color.
 * <p>
 *
 * The user must provide an array of floats specifying how to distribute the
 * colors along the gradient. These values should range from 0.0 to 1.0 and act
 * like keyframes along the gradient (they mark where the gradient should be
 * exactly a particular color).
 *
 * <p>
 * This paint will map the first color of the gradient to a focus point within
 * the circle, and the last color to the perimeter of the circle, interpolating
 * smoothly for any inbetween colors specified by the user. Any line drawn from
 * the focus point to the circumference will span the all the gradient colors.
 * By default the focus is set to be the center of the circle.
 *
 * <p>
 * Specifying a focus point outside of the circle's radius will result in the
 * focus being set to the intersection point of the focus-center line and the
 * perimenter of the circle.
 * <p>
 *
 * Specifying a cycle method allows the user to control the painting behavior
 * outside of the bounds of the circle's radius. See LinearGradientPaint for
 * more details.
 *
 * <p>
 * The following code demonstrates typical usage of RadialGradientPaint:
 * <p>
 * <code>
 * Point2D center = new Point2D.Float(0, 0);<br>
 * float radius = 20;
 * float[] dist = {0.0, 0.2, 1.0};<br>
 * Color[] colors = {Color.red, Color.white, Color.blue};<br>
 * RadialGradientPaint p = new RadialGradientPaint(center, radius, 
 * dist, colors);
 * </code>
 *
 * <p>
 * In the event that the user does not set the first keyframe value equal to 0
 * and the last keyframe value equal to 1, keyframes will be created at these
 * positions and the first and last colors will be replicated there. So, if a
 * user specifies the following arrays to construct a gradient:<br>
 * {Color.blue, Color.red}, {.3, .7}<br>
 * this will be converted to a gradient with the following keyframes:
 * {Color.blue, Color.blue, Color.red, Color.red}, {0, .3, .7, 1}
 *
 *
 * <p>
 * <img src = "radial.jpg" alt="radial">
 * <p>
 * This image demonstrates a radial gradient with NO_CYCLE and default focus.
 * <p>
 *
 * <img src = "radial2.jpg" alt="radial2">
 * <p>
 * This image demonstrates a radial gradient with NO_CYCLE and non-centered
 * focus.
 * <p>
 * 
 * <img src = "radial3.jpg" alt="radial3">
 * <p>
 * This image demonstrates a radial gradient with REFLECT and non-centered
 * focus.
 *
 * @author Nicholas Talian, Vincent Hardy, Jim Graham, Jerry Evans
 * @author <a href="mailto:vincent.hardy@eng.sun.com">Vincent Hardy</a>
 * @version $Id: RadialGradientPaint.java,v 1.1 2004/09/06 19:35:39 kitfox Exp $
 *
 */

public final class RadialGradientPaint extends MultipleGradientPaint {

	/** Focus point which defines the 0% gradient stop x coordinate. */
	private Point2D focus;

	/** Center of the circle defining the 100% gradient stop x coordinate. */
	private Point2D center;

	/** Radius of the outermost circle defining the 100% gradient stop. */
	private float radius;

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>, using the center as the
	 * focus point.
	 *
	 * @param cx
	 *            the x coordinate in user space of the center point of the
	 *            circle defining the gradient. The last color of the gradient
	 *            is mapped to the perimeter of this circle
	 *
	 * @param cy
	 *            the y coordinate in user space of the center point of the
	 *            circle defining the gradient. The last color of the gradient
	 *            is mapped to the perimeter of this circle
	 *
	 * @param radius
	 *            the radius of the circle defining the extents of the color
	 *            gradient
	 *
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 * 
	 *
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 *
	 */
	public RadialGradientPaint(float cx, float cy, float radius,
			float[] fractions, Color[] colors) {
		this(cx, cy, radius, cx, cy, fractions, colors);
	}

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>, using the center as the
	 * focus point.
	 *
	 * @param center
	 *            the center point, in user space, of the circle defining the
	 *            gradient
	 *
	 * @param radius
	 *            the radius of the circle defining the extents of the color
	 *            gradient
	 *
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 * 
	 * @throws NullPointerException
	 *             if center point is null
	 *
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 *
	 */
	public RadialGradientPaint(Point2D center, float radius, float[] fractions,
			Color[] colors) {
		this(center, radius, center, fractions, colors);
	}

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>.
	 *
	 * @param cx
	 *            the x coordinate in user space of the center point of the
	 *            circle defining the gradient. The last color of the gradient
	 *            is mapped to the perimeter of this circle
	 *
	 * @param cy
	 *            the y coordinate in user space of the center point of the
	 *            circle defining the gradient. The last color of the gradient
	 *            is mapped to the perimeter of this circle
	 *
	 * @param radius
	 *            the radius of the circle defining the extents of the color
	 *            gradient
	 *
	 * @param fx
	 *            the x coordinate of the point in user space to which the first
	 *            color is mapped
	 *
	 * @param fy
	 *            the y coordinate of the point in user space to which the first
	 *            color is mapped
	 *
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 * 
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 *
	 */
	public RadialGradientPaint(float cx, float cy, float radius, float fx,
			float fy, float[] fractions, Color[] colors) {
		this(new Point2D.Float(cx, cy), radius, new Point2D.Float(fx, fy),
				fractions, colors, NO_CYCLE, SRGB);
	}

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>.
	 *
	 * @param center
	 *            the center point, in user space, of the circle defining the
	 *            gradient. The last color of the gradient is mapped to the
	 *            perimeter of this circle
	 *
	 * @param radius
	 *            the radius of the circle defining the extents of the color
	 *            gradient
	 *
	 * @param focus
	 *            the point, in user space, to which the first color is mapped
	 *
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 * 
	 * @throws NullPointerException
	 *             if one of the points is null
	 *
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 */
	public RadialGradientPaint(Point2D center, float radius, Point2D focus,
			float[] fractions, Color[] colors) {
		this(center, radius, focus, fractions, colors, NO_CYCLE, SRGB);
	}

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>.
	 *
	 * @param center
	 *            the center point in user space of the circle defining the
	 *            gradient. The last color of the gradient is mapped to the
	 *            perimeter of this circle
	 *
	 * @param radius
	 *            the radius of the circle defining the extents of the color
	 *            gradient
	 *
	 * @param focus
	 *            the point in user space to which the first color is mapped
	 * 
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 *
	 * @param cycleMethod
	 *            either NO_CYCLE, REFLECT, or REPEAT
	 *
	 * @param colorSpace
	 *            which colorspace to use for interpolation, either SRGB or
	 *            LINEAR_RGB
	 * 
	 * @throws NullPointerException
	 *             if one of the points is null
	 *
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 */
	public RadialGradientPaint(Point2D center, float radius, Point2D focus,
			float[] fractions, Color[] colors, CycleMethodEnum cycleMethod,
			ColorSpaceEnum colorSpace) {
		this(center, radius, focus, fractions, colors, cycleMethod, colorSpace,
				new AffineTransform());
	}

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>.
	 *
	 * @param center
	 *            the center point in user space of the circle defining the
	 *            gradient. The last color of the gradient is mapped to the
	 *            perimeter of this circle
	 *
	 * @param radius
	 *            the radius of the circle defining the extents of the color
	 *            gradient.
	 *
	 * @param focus
	 *            the point in user space to which the first color is mapped
	 *
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 *
	 * @param cycleMethod
	 *            either NO_CYCLE, REFLECT, or REPEAT
	 *
	 * @param colorSpace
	 *            which colorspace to use for interpolation, either SRGB or
	 *            LINEAR_RGB
	 *
	 * @param gradientTransform
	 *            transform to apply to the gradient
	 *
	 * @throws NullPointerException
	 *             if one of the points is null, or gradientTransform is null
	 *
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 */
	public RadialGradientPaint(Point2D center, float radius, Point2D focus,
			float[] fractions, Color[] colors, CycleMethodEnum cycleMethod,
			ColorSpaceEnum colorSpace, AffineTransform gradientTransform) {
		super(fractions, colors, cycleMethod, colorSpace, gradientTransform);

		// Check input arguments
		if (center == null) {
			throw new NullPointerException("Center point should not be null.");
		}

		if (focus == null) {
			throw new NullPointerException("Focus point should not be null.");
		}

		if (radius <= 0) {
			throw new IllegalArgumentException(
					"radius should be greater than zero");
		}

		// copy parameters
		this.center = (Point2D) center.clone();
		this.focus = (Point2D) focus.clone();
		this.radius = radius;
	}

	/**
	 * <p>
	 *
	 * Constructs a <code>RadialGradientPaint</code>, the gradient circle is
	 * defined by a bounding box.
	 * 
	 * @param gradientBounds
	 *            the bounding box, in user space, of the circle defining
	 *            outermost extent of the gradient.
	 *
	 * @param fractions
	 *            numbers ranging from 0.0 to 1.0 specifying the distribution of
	 *            colors along the gradient
	 *
	 * @param colors
	 *            array of colors to use in the gradient. The first color is
	 *            used at the focus point, the last color around the perimeter
	 *            of the circle.
	 *
	 * @throws NullPointerException
	 *             if the gradientBounds is null
	 *
	 * @throws IllegalArgumentException
	 *             if fractions.length != colors.length, or if colors is less
	 *             than 2 in size, or if radius &lt; 0
	 *
	 */
	public RadialGradientPaint(Rectangle2D gradientBounds, float[] fractions,
			Color[] colors) {

		// calculate center point and radius based on bounding box coordinates.
		this((float) gradientBounds.getX()
				+ ((float) gradientBounds.getWidth() / 2),

				(float) gradientBounds.getY()
						+ ((float) gradientBounds.getWidth() / 2),

				(float) gradientBounds.getWidth() / 2, fractions, colors);
	}

	/**
	 * <p>
	 * Creates and returns a PaintContext used to generate the color pattern,
	 * for use by the internal rendering engine.
	 *
	 * @param cm
	 *            {@link ColorModel} that receives the <code>Paint</code> data.
	 *            This is used only as a hint.
	 *
	 * @param deviceBounds
	 *            the device space bounding box of the graphics primitive being
	 *            rendered
	 *
	 * @param userBounds
	 *            the user space bounding box of the graphics primitive being
	 *            rendered
	 *
	 * @param transform
	 *            the {@link AffineTransform} from user space into device space
	 *
	 * @param hints
	 *            the hints that the context object uses to choose between
	 *            rendering alternatives
	 *
	 * @return the {@link PaintContext} that generates color patterns.
	 *
	 * @throws IllegalArgumentException
	 *             if the transform is not invertible
	 *
	 * @see PaintContext
	 */
	@Override
	public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
			Rectangle2D userBounds, AffineTransform transform,
			RenderingHints hints) {
		// Can't modify the transform passed in...
		transform = new AffineTransform(transform);
		// incorporate the gradient transform
		transform.concatenate(gradientTransform);

		try {
			return new RadialGradientPaintContext(cm, deviceBounds, userBounds,
					transform, hints, (float) center.getX(),
					(float) center.getY(), radius, (float) focus.getX(),
					(float) focus.getY(), fractions, colors, cycleMethod,
					colorSpace);
		}

		catch (NoninvertibleTransformException e) {
			throw new IllegalArgumentException(
					"transform should be " + "invertible");
		}
	}

	/**
	 * Returns a copy of the center point of the radial gradient.
	 * 
	 * @return a {@link Point2D} object that is a copy of the center point
	 */
	public Point2D getCenterPoint() {
		return new Point2D.Double(center.getX(), center.getY());
	}

	/**
	 * Returns a copy of the end point of the gradient axis.
	 * 
	 * @return a {@link Point2D} object that is a copy of the focus point
	 */
	public Point2D getFocusPoint() {
		return new Point2D.Double(focus.getX(), focus.getY());
	}

	/**
	 * Returns the radius of the circle defining the radial gradient.
	 * 
	 * @return the radius of the circle defining the radial gradient
	 */
	public float getRadius() {
		return radius;
	}

}
